package cn.coder.toolkit;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONReader;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

@Slf4j
@Getter
@Setter
@ToString
@Accessors(chain = true)
public class Universal {

    private Object object; // 调用 method 时需要的对象
    private String name; // 将要被调用的方法的名称, 用与找寻 method
    private Class<?>[] types; // 将要被调用的方法的参数列表类型, 用与找寻 method
    private Object[] values; // 调用 method 时需要的参数列表
    private Method method; // 将要被调用的具体方法
    private Universal next; // 调用下一级方法时的数据封装

    /**
     * 用法
     * parameter:
     *      @Getter
     *      @Setter
     *      @ToString
     *      @NoArgsConstructor
     *      @AllArgsConstructor
     *      public class UniversalExecuteReqDto extends BaseReqDto {
     *          @Serial
     *          private static final long serialVersionUID = 1L;
     *          @NotEmpty
     *          private String bean;
     *          @NotNull
     *          private Object json;
     *      }
     * controller:
     *      @RequestMapping("execute")
     *      public Response execute(@RequestBody UniversalExecuteReqDto request) {
     *          return service.execute(request);
     *      }
     * business:
     *      @Component
     *      public class UniversalBiz implements ApplicationContextAware {
     *          private ApplicationContext context;
     *          public Object execute(UniversalExecuteReqDto request) {
     *              try {
     *                  Object bean = context.getBean(request.getBean());
     *                  return Universal.parse(bean, request.getJson()).invoke();
     *              } catch (Throwable cause) {
     *                  throw new RuntimeException(cause);
     *              }
     *          }
     *          @Override
     *          public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
     *              this.context = applicationContext;
     *          }
     *      }
     * types: 传入右边的字符串即可被解析为左边的类型
     * - 基本数据类型:
     *      byte.class - byte
     * 		short.class - short
     * 		int.class - int
     * 		long.class - long
     * 		float.class - float
     * 		double.class - double
     * 		boolean.class - boolean
     * 		char.class - char
     * - 包装数据类型:
     *  	java.lang.Byte - java.lang.Byte
     * 		java.lang.Short - java.lang.Short
     * 		java.lang.Integer - java.lang.Integer
     * 		java.lang.Long - java.lang.Long
     * 		java.lang.Float - java.lang.Float
     * 		java.lang.Double - java.lang.Double
     * 		java.lang.Boolean - java.lang.Boolean
     * 		java.lang.Character - java.lang.Character
     * - 基本数据类型一维数组:
     *      byte[] - [B
     *      short[] - [S
     *      int[] - [I
     *      long[] - [J
     *      float[] - [F
     *      double[] - [D
     *      boolean[] - [Z
     *      char[] - [C
     * - 包装数据类型一维数组: 用 [L 开头, 用 ; 结尾, 即代表一维数组类型, 用 String 举例, [Ljava.lang.String;
     *      java.lang.Byte[] - [Ljava.lang.Byte;
     *      java.lang.Short[] - [Ljava.lang.Short;
     *      java.lang.Integer[] - [Ljava.lang.Integer;
     *      java.lang.Long[] - [Ljava.lang.Long;
     *      java.lang.Float[] - [Ljava.lang.Float;
     *      java.lang.Double[] - [Ljava.lang.Double;
     *      java.lang.Boolean[] - [Ljava.lang.Boolean;
     *      java.lang.Character[] - [Ljava.lang.Character;
     * - 二维数组: 在一维数组的前面再加一个 [, 如 [[Ljava.lang.String;
     *      byte[][] - [[B
     *      java.lang.Byte[][] - [[Ljava.lang.Byte;
     *      java.lang.String[][] - [[Ljava.lang.String;
     * - 多维数组: N 维数组就是前面 N 个 [ 后面一个 ;
     * - 枚举: types 传入枚举的类, values 传入所选枚举的 name
     * - Map:
     * - Set:
     * - List:
     */
    public static Universal parse(Object bean, Object object) {
        return JSON.parseObject(JSON.toJSONString(object), Universal.class, JSONReader.Feature.SupportClassForName).setObject(bean);
    }

    public Object invoke() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        // 预处理
        preprocess();
        // 执行方法调用
        Object result = this.method.invoke(this.object, this.values);
        if (null == this.next) {
            return result;
        }
        // 设置下一层对象
        this.next.setObject(result);
        // 继续调用下一层
        return this.next.invoke();
    }

    private void preprocess() throws NoSuchMethodException {
        // method
        Method method = this.object.getClass().getMethod(this.name, this.types);
        method.setAccessible(true);
        this.method = method;
        // types -> values
        if (null != this.types) {
            // 可以或许到泛型参数的类型, 如 List<String> 可以获取到 java.util.List<java.lang.Long>
            // Type 转成 ParameterizedType 就可以获取到具体的泛型了
            // genericParameterType instanceof ParameterizedType parameterizedType
            // System.out.println(parameterizedType.getRawType().getTypeName());
            // System.out.println(parameterizedType.getActualTypeArguments()[0].getTypeName());
            Type[] genericParameterTypeArray = method.getGenericParameterTypes();
            for (int i = 0; i < this.types.length; i++) {
                this.values[i] = convert(this.types[i], genericParameterTypeArray[i], this.values[i]);
            }
        }
    }

    private Object convert(Class<?> type, Type genericParameterType, Object value) {
        log.debug("Type: {}, GenericParameterType: {}, Object: {}", type.getTypeName(), genericParameterType.getTypeName(), value);
        Object convert;
        if (type.isArray()) {
            convert = JSON.parseObject(JSON.toJSONString(value), type);
        } else if (type.isEnum()) {
            convert = Arrays.stream(type.getEnumConstants()).filter(item -> item.toString().equals(value.toString())).collect(Collectors.toList()).get(0);
        } else if (Map.class.isAssignableFrom(type)) {
            convert = JSON.parseObject(JSON.toJSONString(value), genericParameterType);
        } else if (Set.class.isAssignableFrom(type)) {
            convert = JSON.parseObject(JSON.toJSONString(value), genericParameterType);
        } else if (List.class.isAssignableFrom(type)) {
            convert = JSON.parseObject(JSON.toJSONString(value), genericParameterType);
        } else if (Collection.class.isAssignableFrom(type)) {
            convert = JSON.parseObject(JSON.toJSONString(value), genericParameterType);
        } else if (Iterable.class.isAssignableFrom(type)) {
            convert = JSON.parseObject(JSON.toJSONString(value), genericParameterType);
        } else if (LocalDateTime.class == type) {
            convert = LocalDateTime.parse(value.toString(), DateTimeKit.Formatter.FORMATTER_DATE_TIME);
        } else if (LocalDate.class == type) {
            convert = LocalDate.parse(value.toString(), DateTimeKit.Formatter.FORMATTER_DATE);
        } else if (LocalTime.class == type) {
            convert = LocalTime.parse(value.toString(), DateTimeKit.Formatter.FORMATTER_TIME);
        } else {
            convert = JSON.parseObject(JSON.toJSONString(value), type, JSONReader.Feature.SupportClassForName);
        }
        log.debug("ConvertedType: {}, ConvertedObject: {}", convert.getClass().getTypeName(), convert);
        return convert;
    }

}
